package client

import (
	"net"
	"reflect"
	"rpc_client_demo/tools"
)

//声明客户端
type Client struct {
	conn net.Conn
}

func NewClient(conn net.Conn) *Client {
	return &Client{conn: conn}
}

// CallRpc
// 实现通用的RPC客户端
// 传入访问的函数名
// fPtr指向的是函数原型
//var select fun xx(user)
//cli.callRPC("selectUser",&select)
func (c *Client) CallRpc(rpcName string, fPtr interface{}) {
	//通过反射 来获取fPtr未初始化的函数原型
	fn := reflect.ValueOf(fPtr).Elem()
	//另外需要一个函数，是对fn函数的操作
	f := func(args []reflect.Value) []reflect.Value {
		//处理参数
		inArgs := make([]interface{}, 0, len(args))
		for _, v := range args {
			inArgs = append(inArgs, v.Interface())
		}
		//连接
		cliSession := tools.NewSession(c.conn)
		//对数据编码
		rpcData := tools.RPCData{
			Name: rpcName,
			Args: inArgs,
		}
		encodeData, err := tools.Encode(rpcData)
		if err != nil {
			panic(err)
		}
		//写数据

		err = cliSession.Write(encodeData)
		if err != nil {
			panic(err)
		}
		//读取服务端返回值
		resp, err := cliSession.Read()
		if err != nil {
			panic(err)
		}
		//解码
		decodeData, err := tools.Decode(resp)
		if err != nil {
			panic(err)
		}
		//处理服务端返回的数据
		outArgs := make([]reflect.Value, 0, len(decodeData.Args))
		for i, arg := range decodeData.Args {
			// 必须进行nil转换
			if arg == nil {
				// reflect.Zero()会返回类型的零值的value
				// .out()会返回函数输出的参数类型
				outArgs = append(outArgs, reflect.Zero(fn.Type().Out(i)))
				continue
			}
			outArgs = append(outArgs, reflect.ValueOf(arg))
		}
		return outArgs
	}
	// 完成原型到函数调用的内部转换
	// 参数1是reflect.Type
	// 参数2 f是函数类型，是对于参数1 fn函数的操作
	// fn是定义，f是具体操作
	v := reflect.MakeFunc(fn.Type(), f)
	fn.Set(v)
}
